/**
 * BroadcastChannel 多标签协同封装
 * - 简易 Leader/Follower：Leader 统一 socket/入库/转发，Follower 只上报本地增量
 * - 通过频道名 bc-${documentId} 将同一文档的标签页连接在一起
 */

export type BCMessage =
  | { type: 'elect-leader'; id: string }
  | { type: 'leader-alive' }
  | { type: 'local-update'; update: ArrayBuffer }
  | { type: 'remote-update'; update: ArrayBuffer }
  | { type: 'awareness'; update: ArrayBuffer };

//   elect-leader：发起选举的探测消息。其它页无需响应，仅作为“我上线了”的信号。
// leader-alive：Leader 的心跳，告知“已有主，不要抢占”。丢失心跳将触发换主。
// local-update：Follower 上报的本地编辑增量，交由 Leader 统一入库与上行。
// remote-update：Leader 将自己应用/收到的最终增量广播给所有页，确保同机多页内容一致。


// 不关心竞态问题，多个领导也没关系 那就相当于没优化呗！！

// 原理
// 进入页面后 会给其他页面 发送一个elect-leader 事件 然后监听 leader-alive 事件，如果收到leader-alive 事件 则自己不当领导。没有收到 leader-alive就当领导。
// elect-leader 当收到 elect-leader事件后 如果自己是领导 则发送 leader-alive 事件 其他页面收到后 不会当领导。

// 然后开启 一个 定时任务，看门扣  各一段时间 检测一下  当前时间减去 最近一次收到领导还活着 的消息的时间，如果 很久没收到这个消息了 则重新发起选举。




export class DocBroadcast {
  private channel: BroadcastChannel;
  private isLeader = false;
  private heartbeatTimer: any = null;
  private readonly HEARTBEAT = 3000;
  private promotionTimer: any = null; // 延迟晋升定时器
  private lastHeartbeatAt = 0; // 最近一次收到 leader 心跳的时间戳
  private watchdogTimer: any = null; // 监测 leader 心跳是否超时
  private onLeaderChangeRef?: (isLeader: boolean) => void; // 页面职责切换回调
  private beforeUnloadHandler = () => {
    // 领导页关闭前尽力再发一次心跳，帮助其他页更快放弃晋升
    if (this.isLeader) this.channel.postMessage({ type: 'leader-alive' } as BCMessage);
  };


  constructor(documentId: string) {
    // 注：不直接保存 documentId，仅用于拼接频道名，避免“未使用”告警
    this.channel = new BroadcastChannel(`bc-${documentId}`);
  }

  /** 开始领导者选举与心跳 */
  // onLeaderChange 页面身份发送变化出发
  startElection(onLeaderChange?: (isLeader: boolean) => void) {
    this.onLeaderChangeRef = onLeaderChange;
    const electId = Math.random().toString(36).slice(2);


    // 先假定自己为 Leader，立刻发送心跳；若 1 个 event loop 内收到其他 Leader 的心跳，则降级
    this.isLeader = true;
    this.onLeaderChangeRef?.(true);
    this.heartbeat();
    // 广播“我上线了”，让其他页有机会宣告已存在的 Leader
    this.channel.postMessage({ type: 'elect-leader', id: electId } as BCMessage);


    // 安排带抖动的晋升定时器
    this.schedulePromotion();

    // 启动看门狗：监测 leader 心跳超时后，自动发起新一轮选举
    this.startWatchdog();

    // 领导页关闭前尽力通知一次
    window.addEventListener('beforeunload', this.beforeUnloadHandler);

    this.channel.onmessage = ev => {
      const msg = ev.data as BCMessage;
      if (!msg || !msg.type) return;
      // 新页面启动会先广播 elect-leader；若当前已是 Leader，则立即回复心跳，告知“已有主”
      if (msg.type === 'elect-leader') {
        // 若双方都自认是 Leader，保留“先占者”：回复心跳，另一侧将降级
        if (this.isLeader) this.channel.postMessage({ type: 'leader-alive' } as BCMessage);
        return;
      }

      if (msg.type === 'leader-alive') {
        // 收到心跳：若当前也自认是 Leader，则降级为 Follower，统一为单 Leader
        if (this.isLeader) {
          this.isLeader = false;
          this.onLeaderChangeRef?.(false);
        }
        // 记录最近心跳并取消“晋升为 Leader”的定时器，避免误晋升
        this.lastHeartbeatAt = Date.now();
        if (this.promotionTimer) { clearTimeout(this.promotionTimer); this.promotionTimer = null; }
        // 接受到这个事件表示 有领导了 自己不当领导。
        onLeaderChange?.(false);
      }
    };
  }

  /** 安排带随机抖动的晋升定时器，并在触发前再次校验最近心跳 */
  // promotionTimer 这个定时器没有被取消则表示 没有其他页面的领导 则自己当领导
  private schedulePromotion() {
    if (this.promotionTimer) { clearTimeout(this.promotionTimer); this.promotionTimer = null; }
    const jitter = 1 + Math.random(); // 1.0~2.0
    this.promotionTimer = setTimeout(() => {
      if (Date.now() - this.lastHeartbeatAt < this.HEARTBEAT) return; // 刚收到心跳，放弃晋升
      if (!this.isLeader) {
        this.isLeader = true;
        this.onLeaderChangeRef?.(true);
        this.heartbeat();
      }
    }, Math.floor(this.HEARTBEAT * 1.5 * jitter));
  }

  /** 看门狗：若长时间收不到 leader 心跳，则重新发起选举 */
  private startWatchdog() {
    if (this.watchdogTimer) return;
    this.watchdogTimer = setInterval(() => {
      if (!this.isLeader && Date.now() - this.lastHeartbeatAt > this.HEARTBEAT * 2) {
        // 如果 很久没有收到 领导或者的 心跳 则重新发起选举。
        // 认为 leader 已离开：再次广播 elect-leader 并重新安排晋升
        this.channel.postMessage({ type: 'elect-leader', id: Math.random().toString(36).slice(2) } as BCMessage);
        this.schedulePromotion();
      }
    }, this.HEARTBEAT);
  }

  /** 周期性发送心跳 */
  private heartbeat() {
    if (!this.isLeader) return;
    // 周期 给其他页面发送 领导还在的事件，其他页面收到后 不会当领导。
    // 如果 其他页面
    this.channel.postMessage({ type: 'leader-alive' } as BCMessage);
    this.heartbeatTimer = setTimeout(() => this.heartbeat(), this.HEARTBEAT);
  }

  /** 发布本地增量（Follower 使用） */
  publishLocalUpdate(buf: ArrayBuffer) {
    // 某些环境的 BroadcastChannel.postMessage 不支持 transfer 列表，移除第二参数确保兼容
    this.channel.postMessage({ type: 'local-update', update: buf } as BCMessage);
  }

  /** 发布远端/最终增量（Leader 使用 → 广播给所有页） */
  // leader 通知其他页面 应用上增量
  publishRemoteUpdate(buf: ArrayBuffer) {
    this.channel.postMessage({ type: 'remote-update', update: buf } as BCMessage);
  }

  /** 发布 awareness 更新（Follower 使用） */
  publishAwareness(buf: ArrayBuffer) {
    this.channel.postMessage({ type: 'awareness', update: buf } as BCMessage);
  }

  /** 订阅消息（由上层根据 isLeader 不同分别处理） */
  onMessage(handler: (msg: BCMessage) => void) {
    this.channel.onmessage = ev => handler(ev.data as BCMessage);
  }

  /**
   * 订阅消息（不会覆盖选举用的 onmessage 处理）
   * - 推荐使用：避免覆盖 startElection 内部绑定的心跳/选举处理器
   * - 返回取消订阅函数
   */
  subscribe(handler: (msg: BCMessage) => void): () => void {
    const fn = (ev: MessageEvent) => handler(ev.data as BCMessage);
    (this.channel as any).addEventListener?.('message', fn);
    return () => {
      try { (this.channel as any).removeEventListener?.('message', fn); } catch { }
    };
  }

  destroy() {
    clearTimeout(this.heartbeatTimer);
    clearTimeout(this.promotionTimer);
    clearInterval(this.watchdogTimer);
    window.removeEventListener('beforeunload', this.beforeUnloadHandler);
    this.channel.close();
  }
}


